package com.bestv.search.engine.command;

import com.bestv.search.common.dao.SearchFieldInfoDao;
import com.bestv.search.common.model.BizType;
import com.bestv.search.common.model.FilterType;
import com.bestv.search.common.model.SearchFieldInfo;
import com.bestv.search.common.model.SearchFilter;
import com.bestv.search.common.util.PropertyStrategyWrapper;
import com.bestv.search.common.util.QueryStringProcess;
import com.bestv.search.common.util.StringConst;
import com.bestv.search.engine.cache.KeySubjectFirstLevelCache;
import com.bestv.search.engine.command.model.*;
import com.bestv.search.engine.compass.extend.CustomSortComparatorSource;
import com.bestv.search.engine.compass.extend.GroupCollector;
import com.bestv.search.engine.compass.spellcheck.SpellChecker;
import com.bestv.search.engine.compass.spellcheck.SpellCheckerWrapper;
import net.paoding.analysis.analyzer.PaodingAnalyzer;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.util.PropertySetStrategy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.document.FieldSelectorResult;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import org.compass.core.Compass;
import org.compass.core.lucene.engine.manager.DefaultLuceneSearchEngineIndexManager;
import org.compass.core.lucene.engine.manager.IndexHoldersCache;
import org.dom4j.Node;

import java.io.IOException;
import java.text.CollationKey;
import java.text.Collator;
import java.util.*;

/**
 * @author Michael Wang
 */

// $Id: LuceneSearchCommandRefactor.java 171 2013-04-18 02:21:02Z luo.xiaojun $
public final class LuceneSearchCommandRefactor {
    protected static final Log log = LogFactory.getLog(LuceneSearchCommandRefactor.class);

    static IndexHoldersCache indexCaches = null;
    static IndexSearcher indexSearcher = null;
    static IndexSearcher keySearcher = null;
    static IndexSearcher hdIndexSearcher = null;
    static String spellCheckerIndexFolder;
    static String[] searchFields = null;
    static Map<String, Float> fieldsBoost = null;
    static SearchFieldInfoDao sfiDao = null;
    static Thread t = null;
    private static LuceneSearchCommandRefactor singletonLuceneSearchCommand;
    private static boolean isWarmed = false;
    private static boolean stop = false;
    private static String[] bizTypesInIndex;
    private static String[] typesInIndex;
    private static String[] bizTypesInHd;
    private static String[] typesInHd;
    private static String[] tagsInHd;
    private static List<BizType> bizTypesList = null;
    private static Map<String, Integer> maxHitCountByTagsMap = null;
    private String defaultSortType;

    private LuceneSearchCommandRefactor() {
    }

    public static LuceneSearchCommandRefactor getSingletonLuceneSearchCommand() {
        if (singletonLuceneSearchCommand == null) {
            singletonLuceneSearchCommand = new LuceneSearchCommandRefactor();
            log.info("</SingletonLuceneSearchCommand is initialized>!");
        }
        return singletonLuceneSearchCommand;
    }

    public static void initStaticFields(Compass compass, SearchFieldInfoDao sfDao) {
        log.info("<intializing static properties for LuceneSearchCommandRefactor>.....");
        LuceneSearchCommandRefactor.indexCaches = ((DefaultLuceneSearchEngineIndexManager) compass
                .getSearchEngineIndexManager()).getIndexHoldersCache();
        LuceneSearchCommandRefactor.sfiDao = sfDao;
        LuceneSearchCommandRefactor.initSearchFields();
        LuceneSearchCommandRefactor.initSearchers();
        if (singletonLuceneSearchCommand == null) {
            singletonLuceneSearchCommand = new LuceneSearchCommandRefactor();
            log.info("<SingletonLuceneSearchCommand is initialized>!");
        }
        if (LuceneSearchCommandRefactor.t == null) {
            LuceneSearchCommandRefactor.t = new Thread(singletonLuceneSearchCommand.new SearchFieldsConfigRunnable());
            t.start();
        }
        log.info("the searchFieldsConfig thread is started...");
        log.info("<static properties of LuceneSearchCommandRefactor initialized> !");
    }

    public static void initSearchFields() {
        log.info("Initializing the searchFieldInfos.....");
        List<SearchFieldInfo> list = sfiDao.getAll();
        if (list == null || list.size() == 0)
            return;
        Set<SearchFieldInfo> sfSet = new HashSet<SearchFieldInfo>(list);
        String[] searchFieldsTemp = new String[sfSet.size()];
        Map<String, Float> fieldsBoostTemp = new HashMap<String, Float>(sfSet.size());
        log.info("searchFieldInfos.size : " + sfSet.size());
        if (searchFields == null)
            searchFields = new String[sfSet.size()];
        if (fieldsBoost == null)
            fieldsBoost = new HashMap<String, Float>(sfSet.size());
        int i = 0;
        for (SearchFieldInfo sf : sfSet) {
            fieldsBoostTemp.put(sf.getName(), sf.getBoost());
            searchFieldsTemp[i++] = sf.getName();
            log.debug(sf);
        }
        fieldsBoost = fieldsBoostTemp;
        searchFields = searchFieldsTemp;
        log.info("the searchFieldInfos intialized !");
    }

    public static void initSearchers() {
        log.info("Initializing the indexSearcher, keySearcher and hdIndexSearcher......");
        indexCaches.refreshCache();
        indexSearcher = indexCaches.getHolder("info").getIndexSearcher();
        keySearcher = indexCaches.getHolder("keysubject").getIndexSearcher();
        hdIndexSearcher = indexCaches.getHolder("hdinfo").getIndexSearcher();
        log.info("the indexSearcher, keySearcher and hdIndexSearcher initialized!");
    }

    public static IndexSearcher refreshIndexCache(String subIndex) {
        log.info("refresh " + subIndex + ".....");
        indexCaches.refreshCache(subIndex);
        return indexCaches.getHolder(subIndex).getIndexSearcher();
    }

    public static void destory() {
        log.info("the searchFieldsConfig thread is stopping....");
        stop = true;
        t.interrupt();
        try {
            indexSearcher.getIndexReader().close();
            keySearcher.getIndexReader().close();
            hdIndexSearcher.getIndexReader().close();
            indexCaches.clearCache();
            KeySubjectFirstLevelCache.stop();
        } catch (IOException e) {
            log.error(e);
        } finally {
            fieldsBoost.clear();
            indexSearcher = null;
            keySearcher = null;
            hdIndexSearcher = null;
            indexCaches = null;
            sfiDao = null;
            searchFields = null;
            fieldsBoost = null;
            singletonLuceneSearchCommand = null;
            KeySubjectFirstLevelCache.getFirstLevelSubjectCache().clear();
            KeySubjectFirstLevelCache.setFirstLevelSubjectCache(null);
            KeySubjectFirstLevelCache.getSecondLevelSubjectCache().clear();
            KeySubjectFirstLevelCache.setSecondLevelSubjectCache(null);
        }
    }

    public static boolean isWarmed() {
        return isWarmed;
    }

    public static void setWarmed(boolean isWarmed) {
        LuceneSearchCommandRefactor.isWarmed = isWarmed;
    }

    public static IndexSearcher getKeySearcher() {
        if (keySearcher == null) {
            indexCaches.refreshCache("keysubject");
            keySearcher = indexCaches.getHolder("keysubject").getIndexSearcher();
            return keySearcher;
        }
        return keySearcher;
    }

    public static void setKeySearcher(IndexSearcher searcher) {
        LuceneSearchCommandRefactor.keySearcher = searcher;
    }

    public static IndexSearcher getIndexSearcher() {
        if (indexSearcher == null) {
            indexCaches.refreshCache("info");
            indexSearcher = indexCaches.getHolder("info").getIndexSearcher();
            return indexSearcher;
        }
        return indexSearcher;
    }

    public static void setIndexSearcher(IndexSearcher searcher) {
        LuceneSearchCommandRefactor.indexSearcher = searcher;
    }

    public static IndexSearcher getHdIndexSearcher() {
        if (hdIndexSearcher == null) {
            indexCaches.refreshCache("hdinfo");
            hdIndexSearcher = indexCaches.getHolder("hdinfo").getIndexSearcher();
            return hdIndexSearcher;
        }
        return hdIndexSearcher;
    }

    public static void setHdIndexSearcher(IndexSearcher searcher) {
        LuceneSearchCommandRefactor.hdIndexSearcher = searcher;
    }

    public static Map<String, Float> getFieldsBoost() {
        return fieldsBoost;
    }

    public static void setSpellCheckerIndexFolder(String spellCheckerIndexFolder) {
        LuceneSearchCommandRefactor.spellCheckerIndexFolder = spellCheckerIndexFolder;
    }

    public static String getPersonDetailsById(String personId, String avatarRoot, int productFlag) {
        BooleanQuery bool = new BooleanQuery();
        try {
            QueryParser idParser = new QueryParser(StringConst.ID, new WhitespaceAnalyzer());
            Query idQuery = idParser.parse(personId);
            bool.add(idQuery, BooleanClause.Occur.MUST);

            QueryParser docTypeParser = new QueryParser(StringConst.P_TYPE, new WhitespaceAnalyzer());
            Query docTypeQuery = docTypeParser.parse(StringConst.PERSON_CHINESE);
            bool.add(docTypeQuery, BooleanClause.Occur.MUST);

            IndexSearcher indexSearcher = getHdIndexSearcher();

            TopFieldDocs collector = indexSearcher.search(bool, null, 1, new Sort());
            if (collector.totalHits == 0) {
                log.warn("Don't find person by id " + personId);
                return null;
            } else {
                ScoreDoc scoreDoc = collector.scoreDocs[0];
                Document doc = indexSearcher.doc(scoreDoc.doc);
                return buildePersonDoc(doc, avatarRoot, productFlag);
            }
        } catch (ParseException e) {
            log.error("Parse query failed " + new Date());
            e.printStackTrace();
        } catch (IOException e) {
            log.error("Get person doc failed " + new Date());
            e.printStackTrace();
        }

        return null;
    }

    private static String buildePersonDoc(Document doc, String avatarRoot, int productFlag) {
        StringBuilder sb = new StringBuilder();

        sb.append("<record>");

        addFieldToDoc(doc, "id", "id", sb);
        addFieldToDoc(doc, "name", "name", sb);

        String avatarDefaultName = doc.get(StringConst.AVATAR_DEFAULT_SRC);
        String avatarIqiyiName = doc.get(StringConst.AVATAR_IQIYI_SRC);
        String avatarYoukuName = doc.get(StringConst.AVATAR_YOUKU_SRC);
        String avatarName = "default.jpg";
        if (avatarDefaultName != null && !StringConst.EMPTY.equals(avatarDefaultName)) {
            avatarName = avatarDefaultName;
        } else if (avatarIqiyiName != null && !StringConst.EMPTY.equals(avatarIqiyiName)) {
            avatarName = avatarIqiyiName;
        } else if (avatarYoukuName != null && !StringConst.EMPTY.equals(avatarYoukuName)) {
            avatarName = avatarYoukuName;
        }
        sb.append("<avatar>");
        sb.append(avatarRoot + avatarName);
        sb.append("</avatar>");

        addFieldToDoc(doc, "gender", "gender", sb);
        addFieldToDoc(doc, "nationality", "nationality", sb);
        addFieldToDoc(doc, "bloodtype", "bloodtype", sb);
        addFieldToDoc(doc, "height", "height", sb);
        addFieldToDoc(doc, "constellation", "constellation", sb);

        String value = doc.get("occupation");
        if (value != null && !value.equals(StringConst.EMPTY) && !value.equals(StringConst.NULLSTRING)) {
            sb.append("<occupation>");
            sb.append(value.replace("[", StringConst.EMPTY).replace("]", StringConst.EMPTY));
            sb.append("</occupation>");
        }

        addFieldToDoc(doc, "birthday", "birthday", sb);
        addAliasNameToDoc(doc, sb);
        addFieldToDoc(doc, "homeplace", "homeplace", sb);
        addFieldToDoc(doc, "description", "description", sb);
        addFieldToDoc(doc, "honor", "honor", sb);

        String honor = doc.get("honor");
        // add products to doc
        addProductsToDoc(doc, sb, productFlag, honor);
        sb.append("</record>");

        return sb.toString();
    }

    private static void addFieldToDoc(Document doc, String luceneFieldName, String xmlFieldName, StringBuilder sb) {
        // Gets filed by name
        String value = doc.get(luceneFieldName);
        if (value != null && !value.equals(StringConst.EMPTY) && !value.equals(StringConst.NULLSTRING)) {
            sb.append("<" + xmlFieldName + ">");
            sb.append(doc.get(luceneFieldName));
            sb.append("</" + xmlFieldName + ">");
        }
    }

    private static void addAliasNameToDoc(Document doc, StringBuilder sb) {
        String aliasName = doc.get("aliasName");

        if (aliasName != null && !aliasName.equals(StringConst.EMPTY) && !aliasName.equals(StringConst.NULLSTRING)) {

            // Convert to PersonProductsJSON Json String
            JSONObject json = new JSONObject();
            json.put("aliasName", aliasName);

            JsonConfig cfg = new JsonConfig();
            cfg.setPropertySetStrategy(new PropertyStrategyWrapper(PropertySetStrategy.DEFAULT));
            AliasNameJSON aliasNameJson = new AliasNameJSON();
            cfg.setRootClass(aliasNameJson.getClass());
            aliasNameJson = (AliasNameJSON) JSONObject.toBean(json, cfg);
            AliasName[] aliasNames = aliasNameJson.getAliasName();
            if (aliasNames != null && aliasNames.length > 0) {
                sb.append("<alias>");
                int i = 0;
                for (AliasName alias : aliasNames) {
                    sb.append(alias.getName());
                    if (i != aliasNames.length - 1) {
                        sb.append(StringConst.COMMA);
                    }
                    i++;
                }
                sb.append("</alias>");
            }
        }
    }

    private static void addProductsToDoc(Document doc, StringBuilder sb, int productFlag, String honor) {
        // Just 0 and 1 are valid values
        // 0: return all products(include online and off line) that related with
        // this person, default value
        // 1: return all products(online product) that related with this person,
        if (productFlag == 0 || productFlag == 1) {
            String products = doc.get("products");

            int productCount = 0;
            String[] honors = null;
            if (honor != null && !StringConst.EMPTY.equals(honor)) {
                honors = honor.split(",");
            }

            if (products != null && !products.equals(StringConst.EMPTY)) {

                // Convert to PersonProductsJSON Json String
                JSONObject json = new JSONObject();
                json.put("personProducts", products);

                JsonConfig cfg = new JsonConfig();
                cfg.setPropertySetStrategy(new PropertyStrategyWrapper(PropertySetStrategy.DEFAULT));
                PersonProductJSONArray personJSON = new PersonProductJSONArray();
                cfg.setRootClass(personJSON.getClass());
                personJSON = (PersonProductJSONArray) JSONObject.toBean(json, cfg);
                PersonProductJSON[] personProducts = personJSON.getPersonProducts();
                if (personProducts != null && personProducts.length > 0) {
                    StringBuilder productSb = new StringBuilder();
                    for (PersonProductJSON product : personProducts) {
                        boolean infoFlag = false;
                        InfoJSON[] infos = product.getInfos();
                        if (infos != null && infos.length > 0) {
                            infoFlag = true;
                        }

                        if (productFlag == 0 || infoFlag) {
                            productSb.append("<item>");
                            productSb.append("<name>");
                            productSb.append(product.getName());
                            productSb.append("</name>");

                            productSb.append("<year>");
                            productSb.append(product.getYear());
                            productSb.append("</year>");

                            productSb.append("<category>");
                            productSb.append(product.getCategory());
                            productSb.append("</category>");

                            productSb.append("<description>");
                            productSb.append(product.getDescription());
                            productSb.append("</description>");

                            String thisHonor = getHonor(honors, product.getName());
                            if (thisHonor != null && !thisHonor.equals(StringConst.EMPTY)) {
                                productSb.append("<honor>");
                                productSb.append(thisHonor);
                                productSb.append("</honor>");
                            }

                            if (productFlag == 0) {
                                productCount++;
                            }
                        }

                        if (infoFlag) {
                            productSb.append("<onlineInfos>");
                            for (InfoJSON onlineInfo : infos) {
                                productSb.append("<info>");

                                productSb.append("<code>");
                                productSb.append(onlineInfo.getCode());
                                productSb.append("</code>");

                                productSb.append("<name>");
                                productSb.append(onlineInfo.getName());
                                productSb.append("</name>");

                                productSb.append("</info>");

                                if (productFlag == 1) {
                                    productCount++;
                                }
                            }
                            productSb.append("</onlineInfos>");
                        }
                        if (productFlag == 0 || infoFlag) {
                            productSb.append("</item>");
                        }
                    }

                    if (productSb.length() > 0) {
                        sb.append("<products>");
                        sb.append(productSb.toString());
                        sb.append("</products>");

                        sb.append("<productCount>");
                        sb.append(productCount);
                        sb.append("</productCount>");
                    }
                }
            }
        }
    }

    private static String getHonor(String[] honors, String infoName) {
        if (honors != null && honors.length > 0) {
            for (String honorText : honors) {
                String[] arrray = honorText.split(StringConst.SPACE);
                if (arrray != null && arrray.length == 4) {
                    if (arrray[3] != null) {
                        String text = arrray[3].replace("《", StringConst.EMPTY).replace("》", StringConst.EMPTY);
                        if (text.equalsIgnoreCase(infoName)) {
                            return arrray[1];
                        }
                    }
                }
            }
        }
        return StringConst.EMPTY;
    }

    public static String[] getBizTypesInIndex() {
        if (bizTypesInIndex == null) {
            try {
                bizTypesInIndex = FieldCache.DEFAULT
                        .getStrings(getIndexSearcher().getIndexReader(), StringConst.P_TYPE);
            } catch (IOException e) {
                log.error("Get bizTypes failed from index, due to exception ");
                e.printStackTrace();
            }
        }
        return bizTypesInIndex;
    }

    public static void setBizTypesInIndex(String[] bizTypesInIndex) {
        LuceneSearchCommandRefactor.bizTypesInIndex = bizTypesInIndex;
    }

    public static String[] getTypesInIndex() {
        if (typesInIndex == null) {
            try {
                typesInIndex = FieldCache.DEFAULT.getStrings(getIndexSearcher().getIndexReader(), StringConst.TYPE);
            } catch (IOException e) {
                log.error("Get types failed, due to exception ");
                e.printStackTrace();
            }
        }
        return typesInIndex;
    }

    public static void setTypesInIndex(String[] typesInIndex) {
        LuceneSearchCommandRefactor.typesInIndex = typesInIndex;
    }

    public static String[] getBizTypesInHd() {
        if (bizTypesInHd == null) {
            try {
                bizTypesInHd = FieldCache.DEFAULT.getStrings(getHdIndexSearcher().getIndexReader(), StringConst.P_TYPE);
            } catch (IOException e) {
                log.error("Get bizTypes failed, due to exception ");
                e.printStackTrace();
            }
        }
        return bizTypesInHd;
    }

    public static void setBizTypesInHd(String[] bizTypesInHd) {
        LuceneSearchCommandRefactor.bizTypesInHd = bizTypesInHd;
    }

    public static String[] getTypesInHd() {
        if (typesInHd == null) {
            try {
                typesInHd = FieldCache.DEFAULT.getStrings(getHdIndexSearcher().getIndexReader(), StringConst.TYPE);
            } catch (IOException e) {
                log.error("Get types failed, due to exception ");
                e.printStackTrace();
            }
        }
        return typesInHd;
    }

    public static void setTypesInHd(String[] typesInHd) {
        LuceneSearchCommandRefactor.typesInHd = typesInHd;
    }

    public static String[] getTagsInHd() {
        if (tagsInHd == null) {
            try {
                tagsInHd = FieldCache.DEFAULT.getStrings(getHdIndexSearcher().getIndexReader(), StringConst.TAGS);
            } catch (IOException e) {
                log.error("Get tags failed, due to exception ");
                e.printStackTrace();
            }
        }
        return tagsInHd;
    }

    public static void setTagsInHd(String[] tagsInHd) {
        LuceneSearchCommandRefactor.tagsInHd = tagsInHd;
    }

    public static Map<String, Integer> getMaxHitCountByTagsMap() {
        if (maxHitCountByTagsMap == null) {
            maxHitCountByTagsMap = new HashMap<String, Integer>();
        }
        return maxHitCountByTagsMap;
    }

    public static void setMaxHitCountByTagsMap(Map<String, Integer> maps) {
        LuceneSearchCommandRefactor.maxHitCountByTagsMap = maps;
    }

    private static String getBizTypeValue(String bizType) {
        if (bizTypesList != null) {
            for (BizType b : bizTypesList) {
                if (b.getPtype().equalsIgnoreCase(bizType)) {
                    return b.getValue();
                }
            }
        }
        return StringConst.OTHER;
    }

    public static void setBizTypeValue(List<BizType> bizTypesList) {
        LuceneSearchCommandRefactor.bizTypesList = bizTypesList;
    }

    @SuppressWarnings("serial")
    public void warm() {
        if (isWarmed())
            return;
        if (indexSearcher == null || keySearcher == null || hdIndexSearcher == null) {
            log.error("indexSearcher or keySearcher or hdIndexSearcher is not initialized,please check the program...");
        } else {
            try {
                log.info("warming the indexSearcher and keySearcher....");
                TopDocs tds = keySearcher.search(new TermQuery(new Term("keysubjectname", "世博历史")), null, 100);
                int i = tds.totalHits;
                Document keyDoc = null;
                if (i > 0) {
                    ScoreDoc[] scoreDocs = tds.scoreDocs;
                    keyDoc = keySearcher.doc(scoreDocs[0].doc, new FieldSelector() {
                        public FieldSelectorResult accept(String fieldName) {
                            if (fieldName.equals("keysubjectname") || fieldName.equals("keysubject_keywords")
                                    || fieldName.equals("p_type"))
                                return FieldSelectorResult.LOAD;
                            return FieldSelectorResult.NO_LOAD;
                        }
                    });
                }
                String keywords = "世博 历史 NBA 中国";
                String p_type = "新闻";
                if (keyDoc != null) {
                    keywords = keyDoc.get("keysubject_keywords");
                    p_type = keyDoc.get("p_type");
                }
                log.info("keywords: " + keywords);
                log.info("p_type：" + p_type);
                BooleanQuery bool = new BooleanQuery();
                QueryParser pq = new QueryParser("keyword", new StandardAnalyzer());
                bool.add(pq.parse(keywords), BooleanClause.Occur.MUST);
                Term term = new Term("p_type", p_type);
                TermQuery tq = new TermQuery(term);
                bool.add(tq, BooleanClause.Occur.MUST);
                log.info(bool.toString());
                TopDocs topDocs = getIndexSearcher().search(bool, null, 5000);
                int total = topDocs.totalHits;
                log.info(total + " document is hitting !");
                ScoreDoc[] sds = topDocs.scoreDocs;
                for (ScoreDoc sd : sds) {
                    Document d = getIndexSearcher().doc(sd.doc, new FieldSelector() {
                        public FieldSelectorResult accept(String fieldName) {
                            if (fieldName.equals("name") || fieldName.equals("keyword") || fieldName.equals("p_type"))
                                return FieldSelectorResult.LOAD;
                            return FieldSelectorResult.NO_LOAD;
                        }
                    });
                    log.info(d);
                }
                setWarmed(true);
                log.info("indexSearcher and keySearcher warmed !");
            } catch (IOException e) {
                log.error(e);
            } catch (ParseException e) {
                log.error(e);
            }
        }
    }

    private String xmlPiece(String code, String message) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><error><code>" + code + "</code><message>" + message
                + "</message></error>";
    }

    @SuppressWarnings("unchecked")
    public final String search(org.dom4j.Document doc, String cmd, String avatarRoot) {
        if (getIndexSearcher() == null || getKeySearcher() == null || getHdIndexSearcher() == null) {
            log.error("The searchers are null!");
            return null;
        }
        String keywords = "";
        String category = "";
        List<SearchFilter> filterList = new ArrayList<SearchFilter>();
        int start = 0, limit = 10;

        Node categoryNode = doc.selectSingleNode("/request/category");
        if (categoryNode != null && !"".equals(categoryNode.getText().trim())) {
            category = categoryNode.getText().trim();
        }

        Node keyNode = doc.selectSingleNode("/request/keywords");
        if (keyNode != null && !"".equals(keyNode.getText().trim())) {
            keywords = keyNode.getText().trim();
        }

        boolean spellChecker = false;
        Node spellCheckNode = doc.selectSingleNode("/request/spellChecker");
        if (spellCheckNode != null && !"".equals(spellCheckNode.getText().trim())) {
            try {
                int spellCheckValue = Integer.valueOf(spellCheckNode.getText().trim());
                spellChecker = spellCheckValue <= 0 ? false : true;
            } catch (Exception e) {
                log.warn("Parse spell check node to integer error!");
                spellChecker = false;
            }
        }

        Node startNode = doc.selectSingleNode("/request/start");
        if (startNode != null && !"".equals(startNode.getText().trim())) {
            try {
                start = Integer.valueOf(startNode.getText());
            } catch (Exception e) {
                log.warn("Parse start node to integer error!");
                start = 0;
            }
        }
        if (start < 0)
            start = 0;

        Node limitNode = doc.selectSingleNode("/request/limit");
        if (limitNode != null && !"".equals(limitNode.getText().trim())) {
            try {
                limit = Integer.valueOf(limitNode.getText());
            } catch (Exception e) {
                log.warn("Parse limit node to integer error!");
                limit = 10;
            }
        }
        if (limit < 0)
            limit = 10;

        List<Node> filterNodes = doc.selectNodes("/request/filter");
        boolean isInfoHeaderItemSearch = false;
        boolean isNewsSearch = false;
        String bizType = StringConst.EMPTY;
        long headerId = 0;
        if (filterNodes != null && filterNodes.size() > 0) {
            for (int i = 0; i < filterNodes.size(); i++) {
                Node filter = filterNodes.get(i);
                SearchFilter sf = new SearchFilter();
                sf.setField(filter.valueOf("./field").trim());
                sf.setValue(filter.valueOf("./value").trim());
                sf.setType(FilterType.OR);
                if ("not".equalsIgnoreCase(filter.valueOf("./type").trim())) {
                    sf.setType(FilterType.NOT);
                } else if ("range".equalsIgnoreCase(filter.valueOf("./type").trim())) {
                    sf.setType(FilterType.RANGE);
                }
                if ("".equals(sf.getField()) || "".equals(sf.getValue()))
                    return xmlPiece("1", "请求消息格式错误");

                if (StringConst.INFO_FLAG.equalsIgnoreCase(sf.getField()) && "1".equals(sf.getValue())
                        && sf.getType() == FilterType.OR) {
                    isInfoHeaderItemSearch = true;
                }

                // 新闻类节目按时间排序
                if ("bizType".equalsIgnoreCase(sf.getField()) && "新闻".equals(sf.getValue().trim()))
                    isNewsSearch = true;

                // Get header id
                // 新闻类节目按时间排序
                if (StringConst.HEADER_ID.equalsIgnoreCase(sf.getField())) {
                    try {
                        headerId = Long.valueOf(sf.getValue().trim());
                    } catch (Exception e) {
                        log.error("Request message is invalid, headerId can't parse to long");
                        return xmlPiece("1", "请求消息格式错误");
                    }
                }

                // Gets bizType
                if ("bizType".equalsIgnoreCase(sf.getField())) {
                    bizType = sf.getValue().trim();
                }

                filterList.add(sf);
            }
        }

        if (StringConst.SEARCHACRONYMCATEGORY.equalsIgnoreCase(cmd) || StringConst.SEARCHACRONYM.equalsIgnoreCase(cmd)) {
            Query query = buildQueryForAcronym(keywords, filterList);
            String sortType = StringConst.DEFAULY;
            if (isNewsSearch) {
                // 新闻类节目按时间排序
                sortType = StringConst.TIME;
            }
            if (isInfoHeaderItemSearch) {
                // 获取剧头的剧集列表
                sortType = StringConst.ITEM_ORDER;
            }
            return searchAcronym(query, sortType, start, limit, cmd, category, keywords, spellChecker, avatarRoot,
                    bizType, headerId);
        } else {
            Query q = buildQuery(keywords, filterList);
            String sortType = getDefaultSortType();
            if (isNewsSearch) {
                // 新闻类节目按时间排序
                sortType = StringConst.TIME;
            }
            if (isInfoHeaderItemSearch) {
                // 获取剧头的剧集列表
                sortType = StringConst.ITEM_ORDER;
            }
            return search(q, sortType, start, limit, cmd, category, keywords, spellChecker, avatarRoot, bizType,
                    headerId);
        }
    }

    private final Query buildQuery(String keywords, List<SearchFilter> filters) {
        String bizTypesStr = null;
        Set<KeySubjectFirstLevelCache.EntityValue> keySubjects = new HashSet<KeySubjectFirstLevelCache.EntityValue>();
        Query q = null;
        if (filters == null || filters.size() == 0) {
            String[] keys = keywords.split("[ ]+");
            for (String keyName : keys) {
                Set<KeySubjectFirstLevelCache.EntityValue> evs = KeySubjectFirstLevelCache.getEntityFromHeap(keyName);
                if (evs != null)
                    keySubjects.addAll(evs);
            }
        } else {
            String[] bizTypes = null;
            for (int i = 0; i < filters.size(); i++) {
                if ("bizType".equalsIgnoreCase(filters.get(i).getField())) {
                    bizTypes = filters.get(i).getValue().split("[ ]+");
                    bizTypesStr = filters.get(i).getValue();
                    break;
                }
            }
            if (bizTypes == null) {
                String[] keyNames = keywords.split("[ ]+");
                for (String keyName : keyNames) {
                    Set<KeySubjectFirstLevelCache.EntityValue> evs = KeySubjectFirstLevelCache
                            .getEntityFromHeap(keyName);
                    if (evs != null)
                        keySubjects.addAll(evs);
                }
            } else {
                String[] keyNames = keywords.split("[ ]+");
                for (String keyName : keyNames) {
                    for (String bizType : bizTypes) {
                        KeySubjectFirstLevelCache.EntityValue ev = KeySubjectFirstLevelCache.getEntityFromHeap(keyName,
                                bizType);
                        if (ev != null) {
                            keySubjects.add(ev);
                        }
                    }
                }
            }
        }
        try {
            q = buildQuery(keywords, bizTypesStr, keySubjects, filters);
        } catch (ParseException e) {
            log.error(e);
        }
        return q;
    }

    private Query buildQuery(String keywords, String bizTypes, Set<KeySubjectFirstLevelCache.EntityValue> keySubjects,
                             List<SearchFilter> filters) throws ParseException {
        boolean isBizTypeNotNull = !(bizTypes == null);
        BooleanQuery bool = new BooleanQuery();
        Analyzer sal = new PaodingAnalyzer();
        MultiFieldQueryParser chineseQp = new MultiFieldQueryParser(getSearchFields(), sal, getFieldBoosts());
        if (keySubjects != null && keySubjects.size() > 0) {
            Iterator<KeySubjectFirstLevelCache.EntityValue> it = keySubjects.iterator();
            while (it.hasNext()) {
                BooleanQuery bq = new BooleanQuery();
                KeySubjectFirstLevelCache.EntityValue ev = it.next();
                Query evKeyQuery = chineseQp.parse(QueryStringProcess.replaceAllIllegleChar(ev.getKeywords()));
                bq.add(evKeyQuery, BooleanClause.Occur.MUST);
                if (ev.getMust() != null && !ev.getMust().matches("[ ]*")) {
                    String[] musts = QueryStringProcess.replaceAllIllegleChar(ev.getMust()).split("[ ]+");
                    for (String must : musts) {
                        Query mustQuery = chineseQp.parse(must);
                        bq.add(mustQuery, BooleanClause.Occur.MUST);
                    }
                }
                if (!ev.getBizType().matches("[ ]*")) {
                    Query bizTypeQuery = new TermQuery(new Term("p_type", QueryStringProcess.replaceAllIllegleChar(ev
                            .getBizType())));
                    bq.add(bizTypeQuery, BooleanClause.Occur.MUST);
                }
                bool.add(bq, BooleanClause.Occur.SHOULD);
            }
        }
        if (!keywords.matches("[ ]*")) {
            // keywords = QueryStringProcess.replaceAllIllegleChar(keywords);
            if (keywords != null) {
                keywords = keywords.trim();
                // Due to exception:
                // org.apache.lucene.queryParser.ParseException: Cannot parse
                // 'OR',
                // Use number instead of keyword "and", "or", "not"
                if (keywords.equalsIgnoreCase(StringConst.AND)) {
                    keywords = "263";
                } else if (keywords.equalsIgnoreCase(StringConst.OR)) {
                    keywords = "67";
                } else if (keywords.equalsIgnoreCase(StringConst.NOT)) {
                    keywords = "668";
                }
            }

            Query keyQuery = chineseQp.parse(keywords);
            bool.add(keyQuery, BooleanClause.Occur.MUST);

            if (isBizTypeNotNull) {
                BooleanQuery bq = new BooleanQuery();
                bq.add(keyQuery, BooleanClause.Occur.MUST);
                if (!bizTypes.matches("[ ]*")) {
                    QueryParser termParserQuery = new QueryParser("p_type", new WhitespaceAnalyzer());
                    Query bizTypeQuery = termParserQuery.parse(QueryStringProcess.replaceAllIllegleChar(bizTypes));
                    bq.add(bizTypeQuery, BooleanClause.Occur.MUST);
                }
                bool.add(bq, BooleanClause.Occur.SHOULD);
            } else {
                bool.add(keyQuery, BooleanClause.Occur.SHOULD);
            }
        }
        if (filters != null && filters.size() != 0) {
            for (int k = 0; k < filters.size(); k++) {
                if ("bizType".equals(filters.get(k).getField())) {
                    QueryParser termParserQuery = new QueryParser("p_type", new WhitespaceAnalyzer());
                    Query bizTypeQuery = termParserQuery.parse(QueryStringProcess.replaceAllIllegleChar(bizTypes));
                    bool.add(bizTypeQuery, filters.get(k).getType() == FilterType.NOT ? BooleanClause.Occur.MUST_NOT
                            : BooleanClause.Occur.MUST);
                } else if (StringConst.HEADER_ID.equalsIgnoreCase(filters.get(k).getField())) {
                    QueryParser headerIdParserQuery = new QueryParser(StringConst.INFOHEADERITEMS,
                            new WhitespaceAnalyzer());
                    Query headerIdQuery = headerIdParserQuery.parse(filters.get(k).getValue());
                    bool.add(headerIdQuery, filters.get(k).getType() == FilterType.NOT ? BooleanClause.Occur.MUST_NOT
                            : BooleanClause.Occur.MUST);
                } else if (filters.get(k).getType() != FilterType.RANGE) {
                    // filters which don't refer to type range
                    QueryParser filterParser = new QueryParser(filters.get(k).getField(), new WhitespaceAnalyzer());
                    Query filterQuery = filterParser.parse(filters.get(k).getValue());
                    bool.add(filterQuery, filters.get(k).getType() == FilterType.NOT ? BooleanClause.Occur.MUST_NOT
                            : BooleanClause.Occur.MUST);
                } else {
                    // filters refering to type range
                    Query rangQuery = null;
                    String[] uts = filters.get(k).getValue().split("TO");
                    if (uts.length == 2) {
                        Term utf = "min".equals(uts[0].trim()) ? null : new Term("updateTimeMillis", uts[0].replaceAll(
                                "[- :]+", ""));
                        Term utt = "max".equals(uts[1].trim()) ? null : new Term("updateTimeMillis", uts[1].replaceAll(
                                "[- :]+", ""));
                        if (utf != null || utt != null)
                            rangQuery = new RangeQuery(utf, utt, true, new Collator() {
                                public int compare(String source, String target) {
                                    long s = Long.valueOf(source);
                                    long t = Long.valueOf(target);
                                    return s > t ? 1 : (s == t ? 0 : -1);
                                }

                                public CollationKey getCollationKey(String source) {
                                    return null;
                                }

                                public int hashCode() {
                                    return 0;
                                }
                            });
                    }
                    if (rangQuery != null) {
                        bool.add(rangQuery, BooleanClause.Occur.MUST);
                    }
                }
            }
        }
        return bool;
    }

    private final void appendDoc(StringBuffer sb, Document doc, int index, float score, String avatarRoot,
                                 String keyword, long header_id) {
        sb.append("<record>");
        sb.append("<index>");
        sb.append(index);
        sb.append("</index>");

        // Gets type
        String type = doc.get("type");
        if (type != null && !"".equals(type)) {
            // transfer series to vod for vis
            sb.append("<type>");
            sb.append(type.equals("series") ? "vod" : type);
            sb.append("</type>");

            // transfer news to vod for vis
            sb.append("<linkType>");
            sb.append(type.equals("news") ? "vod" : type);
            sb.append("</linkType>");
        } else {
            // TODO, for info header, temp set to vod
            sb.append("<type>");
            sb.append("vod");
            sb.append("</type>");

            sb.append("<linkType>");
            sb.append("vod");
            sb.append("</linkType>");
        }

        // Gets code
        String code = null;
        if (type != null && type.equals("schedule")) {
            code = doc.get("channel");
        } else {
            code = doc.get("code");
        }
        sb.append("<value>");
        sb.append(code);
        sb.append("</value>");
        sb.append("<code>");
        sb.append(code);
        sb.append("</code>");

        // Gets name
        String name = doc.get("name");
        sb.append("<name>");
        sb.append("<![CDATA[");
        sb.append(name);
        sb.append("]]></name>");
        if (name != null && !name.equals(StringConst.EMPTY) && keyword != null && !keyword.equals(StringConst.EMPTY)) {
            StringBuilder highWords = new StringBuilder();

            String namePy = doc.get("namePY");
            if (namePy != null && !namePy.equals(StringConst.EMPTY)) {
                // Splits keyword by space
                String[] keywords = keyword.toLowerCase().split(StringConst.SPACE);
                getHighLightWords(name, namePy, keywords, highWords);
                if (highWords.length() == 0) {
                    // Splits keyword by space
                    keywords = keyword.split(StringConst.SPACE);
                    getHighLightWords(name, name, keywords, highWords);
                }
            }

            // Height fields
            if (highWords.length() > 0) {
                sb.append("<highlight>");
                sb.append(highWords.toString());
                sb.append("</highlight>");
            }
        }

        // Gets p_type
        String p_type = doc.get("p_type");
        if (p_type != null) {
            sb.append("<bizType>");
            sb.append(doc.get("p_type"));
            sb.append("</bizType>");
        }

        // Gets bizTypeValue
        String bizTypeValue = doc.get("bizTypeValue");
        if (bizTypeValue != null) {
            sb.append("<bizTypeValue>");
            sb.append(bizTypeValue);
            sb.append("</bizTypeValue>");
        }

        String startTime = doc.get("startTime");
        // if the doc refers to a schedule
        if (startTime != null && !"".equals(startTime)) {
            sb.append("<startTime>");
            sb.append(startTime);
            sb.append("</startTime>");
        }

        String endTime = doc.get("endTime");
        if (endTime != null && !"".equals(endTime)) {
            sb.append("<endTime>");
            sb.append(endTime);
            sb.append("</endTime>");
        }

        String contentFlag = doc.get(StringConst.CONTENT_FLAG);
        if (contentFlag != null && !StringConst.EMPTY.equals(contentFlag)) {
            sb.append("<" + StringConst.CONTENT_FLAG + ">");
            sb.append(contentFlag);
            sb.append("</" + StringConst.CONTENT_FLAG + ">");
        }

        String contentVersion = doc.get(StringConst.CONTENT_VERSION);
        if (contentVersion != null && !StringConst.EMPTY.equals(contentVersion)) {
            sb.append("<" + StringConst.CONTENT_VERSION + ">");
            sb.append(contentVersion);
            sb.append("</" + StringConst.CONTENT_VERSION + ">");
        }

        String infoCount = doc.get(StringConst.INFO_COUNT);
        if (infoCount != null && !StringConst.EMPTY.equals(infoCount)) {
            sb.append("<" + StringConst.INFO_COUNT + ">");
            sb.append(infoCount);
            sb.append("</" + StringConst.INFO_COUNT + ">");
        }

        String infoFlag = doc.get(StringConst.INFO_FLAG);
        if (infoFlag != null && !StringConst.EMPTY.equals(infoFlag)) {
            sb.append("<" + StringConst.INFO_FLAG + ">");
            sb.append(infoFlag);
            sb.append("</" + StringConst.INFO_FLAG + ">");
        }

        String up_flag = doc.get(StringConst.UP_FLAG);
        if (up_flag != null && !StringConst.EMPTY.equals(up_flag)) {
            sb.append("<" + StringConst.UP_FLAG + ">");
            sb.append(up_flag);
            sb.append("</" + StringConst.UP_FLAG + ">");
        }

        String headerType = doc.get(StringConst.HEADER_TYPE);
        if (headerType != null && !StringConst.EMPTY.equals(headerType)) {
            sb.append("<" + StringConst.HEADER_TYPE + ">");
            sb.append(headerType);
            sb.append("</" + StringConst.HEADER_TYPE + ">");
        }

        String id = doc.get(StringConst.ID);
        if (id != null && !StringConst.EMPTY.equals(id)) {
            sb.append("<" + StringConst.ID + ">");
            sb.append(id);
            sb.append("</" + StringConst.ID + ">");
        }

        String items = doc.get("infoHeaderItems");
        if (items != null && !StringConst.EMPTY.equals(items)) {
            // This field's format is
            // "infoHeader_id info_order infoHeader_id info_order"
            // So, get order as below
            String[] itemArray = items.split(StringConst.SPACE);
            for (int i = 0; i < itemArray.length; ) {
                long infoHeader_id = Long.valueOf(itemArray[i]);
                if (infoHeader_id == header_id) {
                    int order = 1;
                    try {
                        order = Integer.valueOf(itemArray[i + 1]);
                    } catch (Exception e) {
                        log.error("Gets order failed for infoHeader:" + infoHeader_id + " and info_code:" + code);
                        order = 1;
                    }
                    sb.append("<" + StringConst.ITEM_ORDER + ">");
                    sb.append(order);
                    sb.append("</" + StringConst.ITEM_ORDER + ">");
                    break;
                }
                i = i + 2;
            }
        }

        if (StringConst.PERSON.equals(bizTypeValue)) {
            // Gets avatar
            String avatarDefaultName = doc.get(StringConst.AVATAR_DEFAULT_SRC);
            String avatarIqiyiName = doc.get(StringConst.AVATAR_IQIYI_SRC);
            String avatarYoukuName = doc.get(StringConst.AVATAR_YOUKU_SRC);
            String avatarName = "default.jpg";
            if (avatarDefaultName != null && !StringConst.EMPTY.equals(avatarDefaultName)) {
                avatarName = avatarDefaultName;
            } else if (avatarIqiyiName != null && !StringConst.EMPTY.equals(avatarIqiyiName)) {
                avatarName = avatarIqiyiName;
            } else if (avatarYoukuName != null && !StringConst.EMPTY.equals(avatarYoukuName)) {
                avatarName = avatarYoukuName;
            }
            sb.append("<avatar>");
            sb.append(avatarRoot + avatarName);
            sb.append("</avatar>");
        }

        sb.append("</record>");
    }

    private void getHighLightWords(String oriStr, String oriIndexStr, String[] highLightWords, StringBuilder highWords) {
        if (highLightWords != null && highLightWords.length > 0) {
            for (int i = 0; i < highLightWords.length; i++) {
                int wordIndex = oriIndexStr.indexOf(highLightWords[i]);
                if (wordIndex != -1) {
                    if (oriStr.length() >= wordIndex + highLightWords[i].length()) {
                        highWords.append(oriStr.substring(wordIndex, wordIndex + highLightWords[i].length()));
                        if (i != highLightWords.length - 1) {
                            highWords.append(StringConst.SPACE);
                        }
                    }
                }
            }
        }
    }

    private final String search(Query q, String sortType, int start, int limit, String cmd, String category,
                                String keywords, boolean spellChecker, String avatarRoot, String bizType, long headerId) {
        Sort sort = new Sort();
        SortField[] sortFields = null;
        if (StringConst.TIME.equals(sortType)) {
            sortFields = new SortField[1];
            sortFields[0] = new SortField(StringConst.UPDATETIMEMILLIS, SortField.LONG, true);
        } else if (StringConst.ITEM_ORDER.equals(sortType)) {
            sortFields = new SortField[1];
            sortFields[0] = new SortField(StringConst.INFOHEADERITEMS, new CustomSortComparatorSource(headerId));
        } else {
            sortFields = new SortField[3];
            sortFields[0] = new SortField(StringConst.UP_FLAG, SortField.INT, true);
            sortFields[1] = new SortField(StringConst.ISCHARGE, SortField.INT, true);
            sortFields[2] = new SortField(StringConst.NAME, SortField.SCORE, false);
        }
        sort.setSort(sortFields);

        if (StringConst.SEARCHCATEGORY.equalsIgnoreCase(cmd)) {
            String fields[] = null;
            String groupBy = null;
            if (StringConst.TYPE.equalsIgnoreCase(category)) {
                fields = getTypesInIndex();
                groupBy = StringConst.TYPE;
            } else {
                fields = getBizTypesInIndex();
                groupBy = StringConst.BIZTYPE;
            }

            GroupCollector groupCollector = null;
            // Gets docs
            TopFieldDocs collector;
            ScoreDoc[] sds_0 = new ScoreDoc[0];
            int totalHits = 0;
            try {
                // Gets group infos
                groupCollector = new GroupCollector(fields);
                getIndexSearcher().search(q, groupCollector);

                // Gets details info
                collector = getIndexSearcher().search(q, null, start + limit, sort);
                totalHits = collector.totalHits;
                sds_0 = collector.scoreDocs;
            } catch (IOException e) {
                log.error(e);
                e.printStackTrace();
            }
            return assembleGroupOutput(groupCollector, groupBy, sds_0, start, limit, totalHits, keywords, spellChecker,
                    avatarRoot, bizType, headerId, false);
        } else {
            TopFieldDocs collector;
            int totalHits = 0;
            ScoreDoc[] sds_0 = new ScoreDoc[0];
            try {
                collector = getIndexSearcher().search(q, null, start + limit, sort);
                totalHits = collector.totalHits;
                sds_0 = collector.scoreDocs;
            } catch (IOException e) {
                log.error(e);
            }
            return assembleOutput(sds_0, start, limit, totalHits, keywords, spellChecker, false, avatarRoot, headerId);
        }
    }

    private String assembleOutput(ScoreDoc[] docs, int start, int limit, int totalHits, String keywords,
                                  boolean spellChecker, boolean isHd, String avatarRoot, long header_id) {
        FieldSelector fs = new FieldSelector() {
            private static final long serialVersionUID = -1870541230131441357L;

            public FieldSelectorResult accept(String fieldName) {
                if ("name".equals(fieldName) || "namePY".equals(fieldName) || "type".equals(fieldName)
                        || "code".equals(fieldName) || "p_type".equals(fieldName) || "startTime".equals(fieldName)
                        || "endTime".equals(fieldName) || "channel".equals(fieldName)
                        || StringConst.DOCTYPE.equals(fieldName) || StringConst.AVATAR_YOUKU_SRC.equals(fieldName)
                        || StringConst.AVATAR_IQIYI_SRC.equals(fieldName)
                        || StringConst.AVATAR_DEFAULT_SRC.equals(fieldName)
                        || StringConst.BIZTYPE_VALUE.equals(fieldName) || StringConst.CONTENT_FLAG.equals(fieldName)
                        || StringConst.CONTENT_VERSION.equals(fieldName) || StringConst.INFO_FLAG.equals(fieldName)
                        || StringConst.INFO_COUNT.equals(fieldName) || StringConst.HEADER_TYPE.equals(fieldName)
                        || StringConst.INFOHEADERITEMS.equals(fieldName) || StringConst.ID.equals(fieldName)
                        || StringConst.UP_FLAG.equals(fieldName))
                    return FieldSelectorResult.LAZY_LOAD;
                return FieldSelectorResult.NO_LOAD;
            }
        };
        StringBuffer result = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?><result><count>");
        result.append(totalHits);
        result.append("</count>");
        int n = totalHits > start + limit ? start + limit : totalHits;
        IndexSearcher indexSearch = null;
        if (isHd) {
            indexSearch = getHdIndexSearcher();
        } else {
            indexSearch = getIndexSearcher();
        }

        for (int i = start; i < n; i++) {
            float score = docs[i].score;
            Document doc = getDocument(indexSearch, docs[i].doc, fs, keywords);
            if (doc != null) {
                appendDoc(result, doc, i, score, avatarRoot, keywords, header_id);
            }
        }
        if (totalHits == 0 && spellChecker) {
            result.append(getSpellCheckerWord(keywords));
        }
        result.append("</result>");
        return result.toString();
    }

    private String getSpellCheckerWord(String keyword) {
        StringBuffer result = new StringBuffer();
        if (keyword != null && !keyword.equals(StringConst.EMPTY)) {
            try {
                SpellChecker spellchecker = SpellCheckerWrapper
                        .getSpellChecker(LuceneSearchCommandRefactor.spellCheckerIndexFolder);
                String[] suggestions = spellchecker.suggestSimilar(keyword, 5);

                result.append("<spellChecker>");
                if (suggestions != null && suggestions.length > 0) {
                    for (String sugget : suggestions) {
                        result.append("<keyword>");
                        result.append(sugget);
                        result.append("</keyword>");
                    }
                }
                result.append("</spellChecker>");
            } catch (IOException e) {
                log.error("Gets spell checker index failed " + e);
                e.printStackTrace();
            }
        }
        return result.toString();
    }

    protected Map<String, Float> getFieldBoosts() {
        if (fieldsBoost != null)
            return fieldsBoost;
        Map<String, Float> map = new HashMap<String, Float>();
        map.put("name", 3.0f);
        map.put("keyword", 2.5f);
        map.put("tag", 2.5f);
        return map;
    }

    protected String[] getSearchFields() {
        if (searchFields != null)
            return searchFields;
        return new String[]{"name", "keyword", "tag"};
    }

    // Add methods for acronym search
    private Query buildQueryForAcronym(String keywords, List<SearchFilter> filters) {
        // build query conditions
        BooleanQuery boolResults = new BooleanQuery();
        try {
            boolean existKeyword = false;
            if (keywords != null && !keywords.matches("[ ]*")) {
                MultiFieldQueryParser nameQp = null;
                if (keywords.length() == 1) {
                    nameQp = new MultiFieldQueryParser(new String[]{"nameFirstChar", "namePYFirstChar"},
                            new WhitespaceAnalyzer());
                } else {
                    nameQp = new MultiFieldQueryParser(new String[]{"name", "namePY"}, new WhitespaceAnalyzer());
                }

                Query query = nameQp.parse(keywords.toLowerCase());
                boolResults.add(query, BooleanClause.Occur.MUST);
                existKeyword = true;
            }

            if (filters != null && filters.size() != 0) {
                for (int k = 0; k < filters.size(); k++) {
                    // SimpleAnalyzer 分析方法：空格及各种符号分割
                    if ((StringConst.GENRE.equalsIgnoreCase(filters.get(k).getField())
                            || StringConst.REGION.equalsIgnoreCase(filters.get(k).getField())
                            || StringConst.CATEGORY.equalsIgnoreCase(filters.get(k).getField())
                            || StringConst.YEAR.equalsIgnoreCase(filters.get(k).getField())
                            || StringConst.LANGUAGE.equalsIgnoreCase(filters.get(k).getField())
                            || StringConst.TAGS.equalsIgnoreCase(filters.get(k).getField()) || StringConst.BIZTYPE
                            .equalsIgnoreCase(filters.get(k).getField()))
                            && filters.get(k).getType() != FilterType.RANGE) {

                        String value = filters.get(k).getValue();
                        if (value != null && !value.matches("[ ]*")) {
                            String filed = filters.get(k).getField();
                            if (filed != null && filed.equalsIgnoreCase(StringConst.BIZTYPE)) {
                                filed = StringConst.P_TYPE;
                            }
                            QueryParser termParserQuery = new QueryParser(filed.toLowerCase(), new WhitespaceAnalyzer());
                            Query query = termParserQuery.parse(value);
                            // 当进行有关键字的查询，且p_type字段包含多个类型时，忽略p_type字段的打分
                            if (existKeyword && StringConst.P_TYPE.equals(filed) && value != null
                                    && value.contains(StringConst.SPACE)) {
                                query.setBoost(0.0f);
                            }
                            boolResults.add(query,
                                    filters.get(k).getType() == FilterType.NOT ? BooleanClause.Occur.MUST_NOT
                                            : BooleanClause.Occur.MUST);
                        }
                    } else if (filters.get(k).getType() != FilterType.RANGE) {
                        String filed = filters.get(k).getField();
                        if (StringConst.HEADER_ID.equalsIgnoreCase(filters.get(k).getField())) {
                            filed = StringConst.INFOHEADERITEMS;
                        }

                        // filters which don't refer to type range
                        QueryParser filterParser = new QueryParser(filed, new WhitespaceAnalyzer());
                        Query filterQuery = filterParser.parse(filters.get(k).getValue());

                        boolResults.add(filterQuery,
                                filters.get(k).getType() == FilterType.NOT ? BooleanClause.Occur.MUST_NOT
                                        : BooleanClause.Occur.MUST);
                    } else {
                        // filters refering to type range
                        Query rangQuery = null;
                        String[] uts = filters.get(k).getValue().split("TO");
                        if (uts.length == 2) {
                            if (StringConst.YEAR.equalsIgnoreCase(filters.get(k).getField())) {
                                Term lowerTerm = StringConst.EMPTY.equals(uts[0].trim())
                                        || StringConst.NULLSTRING.equalsIgnoreCase(uts[0].trim()) ? null : new Term(
                                        StringConst.YEAR, uts[0].trim());
                                Term upperTerm = StringConst.EMPTY.equals(uts[1].trim())
                                        || StringConst.NULLSTRING.equalsIgnoreCase(uts[1].trim()) ? null : new Term(
                                        StringConst.YEAR, uts[1].trim());
                                if (lowerTerm != null || upperTerm != null) {
                                    rangQuery = new RangeQuery(lowerTerm, upperTerm, true);
                                }
                            }
                        }
                        if (rangQuery != null) {
                            boolResults.add(rangQuery, BooleanClause.Occur.MUST);
                        }
                    }
                }
            }
        } catch (ParseException e) {
            log.error("Build query failed @" + new Date());
            e.printStackTrace();
        }

        return boolResults;
    }

    private String searchAcronym(Query q, String sortType, int start, int limit, String cmd, String category,
                                 String keywords, boolean spellChecker, String avatarRoot, String bizType, long headerId) {
        Sort sort = new Sort();
        if (StringConst.ITEM_ORDER.equals(sortType)) {
            sort = new Sort(new SortField(StringConst.INFOHEADERITEMS, new CustomSortComparatorSource(headerId)));
        } else if (StringConst.TIME.equals(sortType)) {
            sort = new Sort(new SortField(StringConst.UPDATETIMEMILLIS, SortField.LONG, true));
        } else {
            SortField[] fields = null;
            if (keywords == null || keywords.length() == 0) {
                // Sort array for 检索
                if (StringConst.DOCUMENTARY_CHINESE.equals(bizType)) {
                    fields = new SortField[4];
                    fields[0] = new SortField(StringConst.UP_FLAG, SortField.INT, true);
                    fields[1] = new SortField(StringConst.TAGS, new CustomSortComparatorSource(getTagsInHd(),
                            getMaxHitCountByTagsMap()));
                    fields[2] = new SortField(StringConst.NAMEFORSORT, SortField.STRING, false);
                    fields[3] = new SortField(StringConst.HITCOUNT, SortField.INT, true);
                } else {
                    fields = new SortField[4];
                    fields[0] = new SortField(StringConst.UP_FLAG, SortField.INT, true);
                    fields[1] = new SortField(StringConst.HITCOUNT, SortField.INT, true);
                    fields[2] = new SortField(StringConst.NAMEFORSORT, SortField.STRING, false);
                    fields[3] = new SortField(StringConst.ISCHARGE, SortField.INT, true);
                }
            } else if (keywords.length() == 1) {
                // Sort array for search by first char
                fields = new SortField[4];
                fields[0] = new SortField(StringConst.UP_FLAG, SortField.INT, true);
                fields[1] = new SortField(StringConst.HITCOUNT, SortField.INT, true);
                fields[2] = new SortField(StringConst.NAMEFORSORT, SortField.STRING, false);
                fields[3] = new SortField(StringConst.ISCHARGE, SortField.INT, true);
            } else {
                // Sort array for others
                fields = new SortField[7];
                fields[0] = new SortField(StringConst.UP_FLAG, SortField.INT, true);
                fields[1] = new SortField(StringConst.BIZTYPE_LEVEL, SortField.INT, false);
                fields[2] = new SortField(StringConst.NAME, SortField.SCORE, false);
                fields[3] = new SortField(StringConst.NAMEPY, SortField.SCORE, false);
                fields[4] = new SortField(StringConst.HD, SortField.INT, true);
                fields[5] = new SortField(StringConst.HITCOUNT, SortField.INT, true);
                fields[6] = new SortField(StringConst.ISCHARGE, SortField.INT, true);
            }
            sort.setSort(fields);
        }

        if (StringConst.SEARCHACRONYMCATEGORY.equalsIgnoreCase(cmd)) {
            String fields[] = null;
            String groupBy = null;
            if (StringConst.TYPE.equalsIgnoreCase(category)) {
                fields = getTypesInHd();
                groupBy = StringConst.TYPE;
            } else {
                fields = getBizTypesInHd();
                groupBy = StringConst.BIZTYPE;
            }

            GroupCollector groupCollector = null;
            // Gets docs
            TopFieldDocs collector;
            ScoreDoc[] sds_0 = new ScoreDoc[0];
            int totalHits = 0;
            try {
                // Gets group infos
                groupCollector = new GroupCollector(fields);
                getHdIndexSearcher().search(q, groupCollector);

                // Gets details info
                collector = getHdIndexSearcher().search(q, null, start + limit, sort);
                totalHits = collector.totalHits;
                sds_0 = collector.scoreDocs;
            } catch (IOException e) {
                log.error(e);
                e.printStackTrace();
            }
            return assembleGroupOutput(groupCollector, groupBy, sds_0, start, limit, totalHits, keywords, spellChecker,
                    avatarRoot, bizType, headerId, true);
        } else {

            TopFieldDocs collector;
            int totalHits = 0;
            ScoreDoc[] sds_0 = new ScoreDoc[0];
            try {
                collector = getHdIndexSearcher().search(q, null, start + limit, sort);
                totalHits = collector.totalHits;
                sds_0 = collector.scoreDocs;

            } catch (IOException e) {
                log.error(e);
            }
            return assembleOutput(sds_0, start, limit, totalHits, keywords, spellChecker, true, avatarRoot, headerId);
        }
    }

    public String assembleGroupOutput(GroupCollector groupCollector, String groupBy, ScoreDoc[] docs, int start,
                                      int limit, int totalHits, String keywords, boolean spellChecker, String avatarRoot, String bizType,
                                      long headerId, boolean isHd) {
        FieldSelector fs = new FieldSelector() {
            private static final long serialVersionUID = -1870541230131441357L;

            public FieldSelectorResult accept(String fieldName) {
                if ("name".equals(fieldName) || "namePY".equals(fieldName) || "type".equals(fieldName)
                        || "code".equals(fieldName) || "p_type".equals(fieldName) || "startTime".equals(fieldName)
                        || "endTime".equals(fieldName) || "channel".equals(fieldName)
                        || StringConst.DOCTYPE.equals(fieldName) || StringConst.AVATAR_YOUKU_SRC.equals(fieldName)
                        || StringConst.AVATAR_IQIYI_SRC.equals(fieldName)
                        || StringConst.AVATAR_DEFAULT_SRC.equals(fieldName)
                        || StringConst.BIZTYPE_VALUE.equals(fieldName) || StringConst.CONTENT_FLAG.equals(fieldName)
                        || StringConst.CONTENT_VERSION.equals(fieldName) || StringConst.INFO_FLAG.equals(fieldName)
                        || StringConst.INFO_COUNT.equals(fieldName) || StringConst.HEADER_TYPE.equals(fieldName)
                        || StringConst.INFOHEADERITEMS.equals(fieldName) || StringConst.ID.equals(fieldName)
                        || StringConst.UP_FLAG.equals(fieldName))
                    return FieldSelectorResult.LAZY_LOAD;
                return FieldSelectorResult.NO_LOAD;
            }
        };
        StringBuffer result = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?><result><count>");
        result.append(totalHits);
        result.append("</count><categories>");
        Map<String, Integer> categoryCount = groupCollector.getGroups();

        String[] bizTypeArray = null;
        if (bizType != null && !bizType.equals(StringConst.EMPTY)) {
            bizTypeArray = bizType.split(StringConst.SPACE);
        }
        if (StringConst.BIZTYPE.equals(groupBy) && bizTypeArray != null && bizTypeArray.length > 0) {
            for (String category : bizTypeArray) {
                Integer count = categoryCount.get(category);
                if (count != null) {
                    result.append("<category><" + groupBy + ">");
                    result.append(category).append("</" + groupBy + ">");
                    if (StringConst.BIZTYPE.equals(groupBy)) {
                        result.append("<bizTypeValue>").append(getBizTypeValue(category)).append("</bizTypeValue>");
                    }
                    result.append("<count>").append(count);
                    result.append("</count></category>");
                }
            }
        } else {
            for (String category : categoryCount.keySet()) {
                result.append("<category><" + groupBy + ">");
                result.append(category).append("</" + groupBy + ">");
                if (StringConst.BIZTYPE.equals(groupBy)) {
                    result.append("<bizTypeValue>").append(getBizTypeValue(category)).append("</bizTypeValue>");
                }
                result.append("<count>").append(categoryCount.get(category));
                result.append("</count></category>");
            }
        }
        result.append("</categories><records>");

        int n = totalHits > start + limit ? start + limit : totalHits;
        IndexSearcher indexSearch = null;
        if (isHd) {
            indexSearch = getHdIndexSearcher();
        } else {
            indexSearch = getIndexSearcher();
        }
        for (int i = start; i < n; i++) {
            float score = docs[i].score;
            Document doc = getDocument(indexSearch, docs[i].doc, fs, keywords);
            if (doc != null) {
                appendDoc(result, doc, i, score, avatarRoot, keywords, headerId);
            }
        }

        result.append("</records>");
        if (totalHits == 0 && spellChecker) {
            result.append(getSpellCheckerWord(keywords));
        }
        result.append("</result>");
        return result.toString();
    }

    private Document getDocument(IndexSearcher indexSearch, int id, FieldSelector fieldSelector, String keywords) {
        Document doc = null;
        try {
            doc = indexSearch.doc(id, fieldSelector);
        } catch (Exception e) {
            doc = null;
            log.error("Access document " + id + " failed by keyword " + keywords + ", " + e.getMessage());
        }

        return doc;
    }

    public String getDefaultSortType() {
        return defaultSortType;
    }

    public void setDefaultSortType(String defaultSortType) {
        this.defaultSortType = defaultSortType;
    }

    private class SearchFieldsConfigRunnable implements Runnable {
        public void run() {
            while (true) {
                if (stop)
                    return;
                try {
                    Thread.sleep(5 * 60 * 1000);
                } catch (InterruptedException e) {
                    if (stop) {
                        log.info("the searchFieldsConfig thread is stopped !");
                        return;
                    }
                    e.printStackTrace();
                }
                initSearchFields();
            }
        }
    }
}